<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="author" content="lijinbo" />
    <title>076-promise初探-下</title>
  </head>

  <body>
    <div class="box">看控制台</div>
    <p>
      参考：
      <a href="https://zhuanlan.zhihu.com/p/144058361">从零开始手写Promise</a>
      <a href="https://www.cnblogs.com/sugar-tomato/p/11353546.html">史上最最最详细的手写Promise教程</a>
    </p>
    <script>
      /**
       * promise 是一个拥有 then 方法的对象或函数，其行为符合本规范；
       * 一个 Promise 的当前状态必须为以下三种状态中的一种：
       * 等待态（Pending）、执行态（Fulfilled）和拒绝态（Rejected）。
       */
      const PENDING = 'pending'
      const FULFILLED = 'fulfilled'
      const REJECTED = 'rejected'
      // resolvePromise 函数让不同的promise代码互相套用
      /*
        1、判断x
        • Otherwise, if x is an object or function,Let then be x.then
        •x 不能是null
        •x 是普通值 直接resolve(x)
        • x 是对象或者函数（包括promise）， let then = x.then 2、当x是对象或者函数（默认promise）
        •声明了then
        •如果取then报错，则走reject()
        •如果then是个函数，则用call执行then，第一个参数是this，后面是成功的回调和失败的回调
        •如果成功的回调还是pormise，就递归继续解析 3、成功和失败只能调用一个 所以设定一个called来防止多次调用
         */
      function resolvePromise(promise2, x, resolve, reject) {
        // 循环引用报错
        if (x === promise2) {
          // reject报错
          return reject(new TypeError('Chaining cycle detected for promise'))
        }
        // 防止多次调用
        let called
        // x不是null 且x是对象或者函数
        if (x != null && (typeof x === 'object' || typeof x === 'function')) {
          try {
            // A+规定，声明then = x的then方法
            let then = x.then
            // 如果then是函数，就默认是promise了
            if (typeof then === 'function') {
              // 就让then执行 第一个参数是this   后面是成功的回调 和 失败的回调
              then.call(
                x,
                (y) => {
                  // 成功和失败只能调用一个
                  if (called) return
                  called = true
                  // resolve的结果依旧是promise 那就继续解析
                  resolvePromise(promise2, y, resolve, reject)
                },
                (err) => {
                  // 成功和失败只能调用一个
                  if (called) return
                  called = true
                  reject(err) // 失败了就失败了
                }
              )
            } else {
              resolve(x) // 直接成功即可
            }
          } catch (e) {
            // 也属于失败
            if (called) return
            called = true
            // 取then出错了那就不要在继续执行了
            reject(e)
          }
        } else {
          resolve(x)
        }
      }
      class Pro {
        constructor(executor) {
          this.state = PENDING // 状态
          this.value = undefined // 成功结果
          this.reason = undefined // 失败原因
          // 使用数组存储回调函数是因为多次调用then时能够正常运行，而变量会被覆盖
          this.onResolvedCallbacks = [] // 存储成功的回调
          this.onRejectedCallbacks = [] // 存储失败的回调
          // 由于状态由pending变为其他状态后不可改变，所以此处需判断状态的可变性
          const resolve = (value) => {
            if (this.state === PENDING) {
              this.state = FULFILLED
              this.value = value
              this.onResolvedCallbacks.forEach((fn) => fn(value))
            }
          }
          const reject = (reason) => {
            if (this.state === PENDING) {
              this.state = REJECTED
              this.reason = reason
              this.onRejectedCallbacks.forEach((fn) => fn(reason))
            }
          }
          try {
            executor(resolve, reject) // 当实例化Promise时，构造函数会马上调用传入的执行函数executor
          } catch (e) {
            reject(e)
          }
        }
        // A+规定：then 方法必须返回一个 promise 对象，以便后面进行链式调用
        then(onFulfilled, onRejected) {
          // onFulfilled如果不是函数，就忽略onFulfilled，直接返回value
          onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value
          // onRejected如果不是函数，就忽略onRejected，直接扔出错误
          onRejected =
            typeof onRejected === 'function'
              ? onRejected
              : (err) => {
                  throw err
                }
          /**
                  A+规定：
                  1、onFulfilled,onRejected都是可选参数，如果他们不是函数，必须被忽略
                  • onFulfilled返回一个普通的值，成功时直接等于 value => value
                  • onRejected返回一个普通的值，失败时如果直接等于 value => value，则会跑到下一个then中的onFulfilled中，所以直接扔出一个错误 reason => throw err 
                  2、onFulfilled或onRejected不能同步被调用，必须异步调用。我们就用setTimeout解决异步问题
                  •如果onFulfilled或onRejected报错，则直接返回reject()
                 */
          // 声明返回的promise2
          let promise2 = new Pro((resolve, reject) => {
            if (this.state === 'fulfilled') {
              // 异步
              setTimeout(() => {
                try {
                  let x = onFulfilled(this.value)
                  resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                  reject(e)
                }
              }, 0)
            }
            if (this.state === 'rejected') {
              // 异步
              setTimeout(() => {
                // 如果报错
                try {
                  let x = onRejected(this.reason)
                  resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                  reject(e)
                }
              }, 0)
            }
            if (this.state === 'pending') {
              this.onResolvedCallbacks.push(() => {
                // 异步
                setTimeout(() => {
                  try {
                    let x = onFulfilled(this.value)
                    resolvePromise(promise2, x, resolve, reject)
                  } catch (e) {
                    reject(e)
                  }
                }, 0)
              })
              this.onRejectedCallbacks.push(() => {
                // 异步
                setTimeout(() => {
                  try {
                    let x = onRejected(this.reason)
                    resolvePromise(promise2, x, resolve, reject)
                  } catch (e) {
                    reject(e)
                  }
                }, 0)
              })
            }
          })
          // 返回promise，完成链式
          return promise2
        }
        catch(fn) {
          return this.then(null, fn)
        }
      }

      // 手写 promise 测试
      function api(time = 500) {
        return new Pro(function (resolve, reject) {
          let ranNum = Math.random() * 100
          setTimeout(function () {
            if (ranNum < 30) {
              reject(ranNum)
            } else {
              resolve(ranNum)
            }
          }, time)
        })
      }

      api(500)
        .then((res) => {
          console.log(res, '-->>> res 500')
          return api(300)
        })
        .then((res) => {
          console.log(res, '-->>> res 300')
          return api(100)
        })
        .then((res) => {
          console.log(res, '-->>> res 100')
        })
        .catch((err) => {
          console.log(err, '-->>> err')
        })
    </script>
  </body>
</html>
